Интеграционные тесты на дедлоки и одновременные запросы
Если вы так же озабочены тестированием как и я, то вы возможно сталкивались с проблемами дедлоков при транзакциях. Транзакции в БД дело хорошее, особенно на все REST-запросы, т.к. он становится атомарной операцией. Однако если вы вместе с этим затрагиваете часто используемую таблицу или операция происходит на файлах или с другими сетевыми запросами, то вы можете вызвать дедлок.
MySQL/InnoDB дедлок возникает из-за двух транзакций пытающихся в разном по рядке изменить одни и те же данные. Если вы используете триггеры, то вам в коде даже не надо явно объявлять о транзакции - любой UPDATE с триггером потенциально опасен дедлоком. При этом уровень изоляции транзакции не влияет на вероятность его получения
Детали в базе можно увидеть так:
SHOW ENGINE InnoDB STATUS;
------------------------
LATEST DETECTED DEADLOCK
------------------------
2016-10-21 08:21:41 0x7f772b726700
*** (1) TRANSACTION:
TRANSACTION 9651, ACTIVE 0 sec starting index read
mysql tables in use 3, locked 3
LOCK WAIT 4 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 1052, OS thread handle 140149807552256, query id 128292 localhost 127.0.0.1 tactic updating
UPDATE folder c SET (...)
*** (2) TRANSACTION:
TRANSACTION 9650, ACTIVE 0 sec fetching rows
mysql tables in use 3, locked 3
8 lock struct(s), heap size 1136, 183 row lock(s), undo log entries 1
MySQL thread id 1048, OS thread handle 140149806753536, query id 128291 localhost 127.0.0.1 tactic Sending data
UPDATE folder c SET (...)
Обычно советуют в случае дедлока перезапускать всю операцию, но это может усложнить весь метод.
Если вы не хотите усложнять себе жизнь обработкой одновременных запросов, можно сделать лок на весь POST-запрос, скажем используя memcache.
Так или иначе, в итоге вы хотите протестировать что два или более одновременных REST-запроса на один и тот же URL не упадут используя PHPUnit. В php это реализуется методом curl_multi_init:
function TwoConcurrentSaves_ShouldNotCauseError() {
$urls = [
'http://kurapov.ee/file/save',
'http://kurapov.ee/file/save',
];
$node_count = count($urls);
$chs = [];
$master = curl_multi_init();
for ($i = 0; $i < $node_count; $i++) {
$url = $urls[$i];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, ['collectionID' => 13, 'quality'=> 90]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_multi_add_handle($master, $ch);
$chs[$i] = $ch;
}
do {
curl_multi_exec($master, $running);
} while ($running > 0);
for ($i = 0; $i < $node_count; $i++) {
$response = curl_multi_getcontent($chs[$i]);
$this->assertNotContains('Serialization failure', $response);
$this->assertNotContains('Deadlock found', $response);
}
}